#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>

#define TIMERDEV_NAME		"timer"
#define TIMERDEV_CNT		1
#define CLOSE_CMD 			(_IO(0XEF, 0x1))
#define OPEN_CMD 			(_IO(0XEF, 0x2))
#define SETPEROD_CMD 		(_IO(0XEF, 0x3))

struct timerdev_st
{
	dev_t devid;
	int major;
	int minor;
	int led_gpio;
	int timerperiod; 		//单位ms
	spinlock_t spinlock;
	struct cdev cdev;
	struct class *class;
	struct device *device;
	struct device_node *node;
	struct timer_list timer;
};

static struct timerdev_st timerdev;

static int led_init(void)
{
	int ret =0;

	timerdev.node = of_find_node_by_path("/leds/led2");
	if(timerdev.node ==NULL)
		return -EINVAL;

	timerdev.led_gpio = of_get_named_gpio(timerdev.node, "gpios", 0);
	if(timerdev.led_gpio <0)
		return -EINVAL;

	ret = gpio_request(timerdev.led_gpio, "led");
	if(ret <0)
		return -EINVAL;

	ret = gpio_direction_output(timerdev.led_gpio, 1);
	if(ret <0)
		return -EINVAL;

	return 0;	
}

static int timerdev_open(struct inode *inode, struct file *flip)
{
	int ret =0;

	flip->private_data = &timerdev;

	timerdev.timerperiod = 1000;
	ret = led_init();
	if(ret <0)
		return ret;
	
	return 0;
}

static long timerdev_unlocked_ioctl(struct file *flip, unsigned int cmd, unsigned long arg)
{
	struct timerdev_st *me = (struct timerdev_st *)flip->private_data;
	int timerperiod; 
	unsigned long flags;

	switch(cmd)
	{
		case CLOSE_CMD:
			del_timer_sync(&me->timer);
			break;
	
		case OPEN_CMD:
			spin_lock_irqsave(&me->spinlock,flags);
			timerperiod = me->timerperiod;
			spin_unlock_irqrestore(&me->spinlock,flags);
			mod_timer(&me->timer,jiffies+msecs_to_jiffies(timerperiod));
			break;

		case SETPEROD_CMD:
			spin_lock_irqsave(&me->spinlock,flags);
			me->timerperiod = arg;
			spin_unlock_irqrestore(&me->spinlock,flags);
			mod_timer(&me->timer,jiffies+msecs_to_jiffies(arg));
			break;

		default:
			break;
	}
	
	return 0;
}

static struct file_operations timerdev_ops = 
{
	.owner 			= THIS_MODULE,
	.open		 	= timerdev_open,
	.unlocked_ioctl = timerdev_unlocked_ioctl,
};

static void timer_function(struct timer_list * timer)
{
	static int stat =1;
	int timerperiod;
	unsigned long flags;

	stat =!stat;
	gpio_set_value(timerdev.led_gpio, stat);

	//重启定时器(自旋锁)
	spin_lock_irqsave(&timerdev.spinlock,flags);
	timerperiod = timerdev.timerperiod;
	spin_unlock_irqrestore(&timerdev.spinlock,flags);
	mod_timer(&timerdev.timer,jiffies+msecs_to_jiffies(timerperiod));
}

static int __init timer_init(void) 
{
	int ret =0;

	//申请设备号
	if(timerdev.major)
	{
		timerdev.devid = MKDEV(timerdev.major, timerdev.minor);
		ret = register_chrdev_region(timerdev.devid, TIMERDEV_CNT, TIMERDEV_NAME);
		if(ret <0)
		{
			return -EINVAL;
		}
	}
	else
	{
		ret = alloc_chrdev_region(&timerdev.devid, 0, TIMERDEV_CNT, TIMERDEV_NAME);
		if(ret <0)
		{

		}
		timerdev.major = MAJOR(timerdev.devid);
		timerdev.minor = MINOR(timerdev.devid);
	}

	//注册字符设备
	timerdev.cdev.owner = THIS_MODULE;
	cdev_init(&timerdev.cdev, &timerdev_ops);
	ret = cdev_add(&timerdev.cdev, timerdev.devid, TIMERDEV_CNT);
	if(ret <0)
	{

	}

	//创建类
	timerdev.class = class_create(THIS_MODULE, TIMERDEV_NAME);
	if(IS_ERR(timerdev.class))
	{
		return PTR_ERR(timerdev.class);
	}

	//创建设备
	timerdev.device = device_create(timerdev.class, NULL, timerdev.devid, NULL, TIMERDEV_NAME);
	if(IS_ERR(timerdev.device))
	{
		return PTR_ERR(timerdev.device);
	}

	//初始化定时器
	timer_setup(&timerdev.timer, timer_function, 0);

	printk("timer init sucess\r\n");
	return 0;
}

static void __exit timer_exit(void)
{
	//释放gpio
	gpio_free(timerdev.led_gpio);

	//删除定时器
	del_timer_sync(&timerdev.timer);

	//删除字符设备
	cdev_del(&timerdev.cdev);
	unregister_chrdev_region(timerdev.devid, TIMERDEV_CNT);

	//销毁设备、类
	device_destroy(timerdev.class, timerdev.devid);
	class_destroy(timerdev.class);

	printk("timer exit sucess\r\n");
}

module_init(timer_init);
module_exit(timer_exit);
MODULE_AUTHOR("jtb");
MODULE_LICENSE("GPL");

